// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator.
// Copyright (C) 1999-2003 Forgotten
// Copyright (C) 2004 Forgotten and the VBA development team

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or(at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "../Port.h"
#include "../NLS.h"
#include "../common/System.h" // systemMessage
#include "Globals.h"
#include "elf.h"

#define elfReadMemory(addr) \
    READ32LE((&map[(addr)>>24].address[(addr) & map[(addr)>>24].mask]))

#define DW_TAG_array_type             0x01
#define DW_TAG_enumeration_type       0x04
#define DW_TAG_formal_parameter       0x05
#define DW_TAG_label                  0x0a
#define DW_TAG_lexical_block          0x0b
#define DW_TAG_member                 0x0d
#define DW_TAG_pointer_type           0x0f
#define DW_TAG_reference_type         0x10
#define DW_TAG_compile_unit           0x11
#define DW_TAG_structure_type         0x13
#define DW_TAG_subroutine_type        0x15
#define DW_TAG_typedef                0x16
#define DW_TAG_union_type             0x17
#define DW_TAG_unspecified_parameters 0x18
#define DW_TAG_inheritance            0x1c
#define DW_TAG_inlined_subroutine     0x1d
#define DW_TAG_subrange_type          0x21
#define DW_TAG_base_type              0x24
#define DW_TAG_const_type             0x26
#define DW_TAG_enumerator             0x28
#define DW_TAG_subprogram             0x2e
#define DW_TAG_variable               0x34
#define DW_TAG_volatile_type          0x35

#define DW_AT_sibling              0x01
#define DW_AT_location             0x02
#define DW_AT_name                 0x03
#define DW_AT_byte_size            0x0b
#define DW_AT_bit_offset           0x0c
#define DW_AT_bit_size             0x0d
#define DW_AT_stmt_list            0x10
#define DW_AT_low_pc               0x11
#define DW_AT_high_pc              0x12
#define DW_AT_language             0x13
#define DW_AT_compdir              0x1b
#define DW_AT_const_value          0x1c
#define DW_AT_containing_type      0x1d
#define DW_AT_inline               0x20
#define DW_AT_producer             0x25
#define DW_AT_prototyped           0x27
#define DW_AT_upper_bound          0x2f
#define DW_AT_abstract_origin      0x31
#define DW_AT_accessibility        0x32
#define DW_AT_artificial           0x34
#define DW_AT_data_member_location 0x38
#define DW_AT_decl_file            0x3a
#define DW_AT_decl_line            0x3b
#define DW_AT_declaration          0x3c
#define DW_AT_encoding             0x3e
#define DW_AT_external             0x3f
#define DW_AT_frame_base           0x40
#define DW_AT_macro_info           0x43
#define DW_AT_specification        0x47
#define DW_AT_type                 0x49
#define DW_AT_virtuality           0x4c
#define DW_AT_vtable_elem_location 0x4d
// DWARF 2.1/3.0 extensions
#define DW_AT_entry_pc             0x52
#define DW_AT_ranges               0x55
// ARM Compiler extensions
#define DW_AT_proc_body            0x2000
#define DW_AT_save_offset          0x2001
#define DW_AT_user_2002            0x2002
// MIPS extensions
#define DW_AT_MIPS_linkage_name    0x2007

#define DW_FORM_addr      0x01
#define DW_FORM_data2     0x05
#define DW_FORM_data4     0x06
#define DW_FORM_string    0x08
#define DW_FORM_block     0x09
#define DW_FORM_block1    0x0a
#define DW_FORM_data1     0x0b
#define DW_FORM_flag      0x0c
#define DW_FORM_sdata     0x0d
#define DW_FORM_strp      0x0e
#define DW_FORM_udata     0x0f
#define DW_FORM_ref_addr  0x10
#define DW_FORM_ref4      0x13
#define DW_FORM_ref_udata 0x15
#define DW_FORM_indirect  0x16

#define DW_OP_addr        0x03
#define DW_OP_plus_uconst 0x23
#define DW_OP_reg0        0x50
#define DW_OP_reg1        0x51
#define DW_OP_reg2        0x52
#define DW_OP_reg3        0x53
#define DW_OP_reg4        0x54
#define DW_OP_reg5        0x55
#define DW_OP_reg6        0x56
#define DW_OP_reg7        0x57
#define DW_OP_reg8        0x58
#define DW_OP_reg9        0x59
#define DW_OP_reg10       0x5a
#define DW_OP_reg11       0x5b
#define DW_OP_reg12       0x5c
#define DW_OP_reg13       0x5d
#define DW_OP_reg14       0x5e
#define DW_OP_reg15       0x5f
#define DW_OP_fbreg       0x91

#define DW_LNS_extended_op      0x00
#define DW_LNS_copy             0x01
#define DW_LNS_advance_pc       0x02
#define DW_LNS_advance_line     0x03
#define DW_LNS_set_file         0x04
#define DW_LNS_set_column       0x05
#define DW_LNS_negate_stmt      0x06
#define DW_LNS_set_basic_block  0x07
#define DW_LNS_const_add_pc     0x08
#define DW_LNS_fixed_advance_pc 0x09

#define DW_LNE_end_sequence 0x01
#define DW_LNE_set_address  0x02
#define DW_LNE_define_file  0x03

#define DW_CFA_advance_loc      0x01
#define DW_CFA_offset           0x02
#define DW_CFA_restore          0x03
#define DW_CFA_set_loc          0x01
#define DW_CFA_advance_loc1     0x02
#define DW_CFA_advance_loc2     0x03
#define DW_CFA_advance_loc4     0x04
#define DW_CFA_offset_extended  0x05
#define DW_CFA_restore_extended 0x06
#define DW_CFA_undefined        0x07
#define DW_CFA_same_value       0x08
#define DW_CFA_register         0x09
#define DW_CFA_remember_state   0x0a
#define DW_CFA_restore_state    0x0b
#define DW_CFA_def_cfa          0x0c
#define DW_CFA_def_cfa_register 0x0d
#define DW_CFA_def_cfa_offset   0x0e
#define DW_CFA_nop              0x00

#define CASE_TYPE_TAG \
case DW_TAG_const_type: \
case DW_TAG_volatile_type: \
case DW_TAG_pointer_type: \
case DW_TAG_base_type: \
case DW_TAG_array_type: \
case DW_TAG_structure_type: \
case DW_TAG_union_type: \
case DW_TAG_typedef: \
case DW_TAG_subroutine_type: \
case DW_TAG_enumeration_type: \
case DW_TAG_enumerator: \
case DW_TAG_reference_type

struct ELFcie
{
	ELFcie *next;
	u32     offset;
	u8 *    augmentation;
	u32     codeAlign;
	s32     dataAlign;
	int     returnAddress;
	u8 *    data;
	u32     dataLen;
};

struct ELFfde
{
	ELFcie *cie;
	u32     address;
	u32     end;
	u8 *    data;
	u32     dataLen;
};

enum ELFRegMode
{
	REG_NOT_SET,
	REG_OFFSET,
	REG_REGISTER
};

struct ELFFrameStateRegister
{
	ELFRegMode mode;
	int        reg;
	s32        offset;
};

struct ELFFrameStateRegisters
{
	ELFFrameStateRegister   regs[16];
	ELFFrameStateRegisters *previous;
};

enum ELFCfaMode
{
	CFA_NOT_SET,
	CFA_REG_OFFSET
};

struct ELFFrameState
{
	ELFFrameStateRegisters registers;

	ELFCfaMode cfaMode;
	int        cfaRegister;
	s32        cfaOffset;

	u32 pc;

	int dataAlign;
	int codeAlign;
	int returnAddress;
};

extern bool8 cpuIsMultiBoot;

Symbol *elfSymbols       = NULL;
char *  elfSymbolsStrTab = NULL;
int     elfSymbolsCount  = 0;

ELFSectionHeader **elfSectionHeaders = NULL;
char *elfSectionHeadersStringTable   = NULL;
int   elfSectionHeadersCount         = 0;
u8 *  elfFileData = NULL;

CompileUnit *elfCompileUnits = NULL;
DebugInfo *  elfDebugInfo    = NULL;
char *       elfDebugStrings = NULL;

ELFcie * elfCies     = NULL;
ELFfde **elfFdes     = NULL;
int      elfFdeCount = 0;

CompileUnit *elfCurrentUnit = NULL;

u32 elfRead4Bytes(u8 *);
u16 elfRead2Bytes(u8 *);

CompileUnit *elfGetCompileUnit(u32 addr)
{
	if (elfCompileUnits)
	{
		CompileUnit *unit = elfCompileUnits;
		while (unit)
		{
			if (unit->lowPC)
			{
				if (addr >= unit->lowPC && addr < unit->highPC)
					return unit;
			}
			else
			{
				ARanges *r = unit->ranges;
				if (r)
				{
					int count = r->count;
					for (int j = 0; j < count; j++)
					{
						if (addr >= r->ranges[j].lowPC && addr < r->ranges[j].highPC)
							return unit;
					}
				}
			}
			unit = unit->next;
		}
	}
	return NULL;
}

char *elfGetAddressSymbol(u32 addr)
{
	static char buffer[256];

	CompileUnit *unit = elfGetCompileUnit(addr);
	// found unit, need to find function
	if (unit)
	{
		Function *func = unit->functions;
		while (func)
		{
			if (addr >= func->lowPC && addr < func->highPC)
			{
				int   offset = addr - func->lowPC;
				char *name   = func->name;
				if (!name)
					name = "";
				if (offset)
					sprintf(buffer, "%s+%d", name, offset);
				else
					strcpy(buffer, name);
				return buffer;
			}
			func = func->next;
		}
	}

	if (elfSymbolsCount)
	{
		for (int i = 0; i < elfSymbolsCount; i++)
		{
			Symbol *s = &elfSymbols[i];
			if ((addr >= s->value)  && addr < (s->value+s->size))
			{
				int   offset = addr-s->value;
				char *name   = s->name;
				if (name == NULL)
					name = "";
				if (offset)
					sprintf(buffer, "%s+%d", name, addr-s->value);
				else
					strcpy(buffer, name);
				return buffer;
			}
			else if (addr == s->value)
			{
				if (s->name)
					strcpy(buffer, s->name);
				else
					strcpy(buffer, "");
				return buffer;
			}
		}
	}

	return "";
}

bool elfFindLineInModule(u32 *addr, char *name, int line)
{
	CompileUnit *unit = elfCompileUnits;

	while (unit)
	{
		if (unit->lineInfoTable)
		{
			int   i;
			int   count = unit->lineInfoTable->fileCount;
			char *found = NULL;
			for (i = 0; i < count; i++)
			{
				if (strcmp(name, unit->lineInfoTable->files[i]) == 0)
				{
					found = unit->lineInfoTable->files[i];
					break;
				}
			}
			// found a matching filename... try to find line now
			if (found)
			{
				LineInfoItem *table = unit->lineInfoTable->lines;
				count = unit->lineInfoTable->number;
				for (i = 0; i < count; i++)
				{
					if (table[i].file == found && table[i].line == line)
					{
						*addr = table[i].address;
						return true;
					}
				}
				// we can only find a single match
				return false;
			}
		}
		unit = unit->next;
	}
	return false;
}

int elfFindLine(CompileUnit *unit, Function * /* func */, u32 addr, char **f)
{
	int currentLine = -1;
	if (unit->hasLineInfo)
	{
		int count = unit->lineInfoTable->number;
		LineInfoItem *table = unit->lineInfoTable->lines;
		int i;
		for (i = 0; i < count; i++)
		{
			if (addr <= table[i].address)
				break;
		}
		if (i == count)
			i--;
		*f = table[i].file;
		currentLine = table[i].line;
	}
	return currentLine;
}

bool elfFindLineInUnit(u32 *addr, CompileUnit *unit, int line)
{
	if (unit->hasLineInfo)
	{
		int count = unit->lineInfoTable->number;
		LineInfoItem *table = unit->lineInfoTable->lines;
		int i;
		for (i = 0; i < count; i++)
		{
			if (line == table[i].line)
			{
				*addr = table[i].address;
				return true;
			}
		}
	}
	return false;
}

bool elfGetCurrentFunction(u32 addr, Function **f, CompileUnit **u)
{
	CompileUnit *unit = elfGetCompileUnit(addr);
	// found unit, need to find function
	if (unit)
	{
		Function *func = unit->functions;
		while (func)
		{
			if (addr >= func->lowPC && addr < func->highPC)
			{
				*f = func;
				*u = unit;
				return true;
			}
			func = func->next;
		}
	}
	return false;
}

bool elfGetObject(char *name, Function *f, CompileUnit *u, Object **o)
{
	if (f && u)
	{
		Object *v = f->variables;

		while (v)
		{
			if (strcmp(name, v->name) == 0)
			{
				*o = v;
				return true;
			}
			v = v->next;
		}
		v = f->parameters;
		while (v)
		{
			if (strcmp(name, v->name) == 0)
			{
				*o = v;
				return true;
			}
			v = v->next;
		}
		v = u->variables;
		while (v)
		{
			if (strcmp(name, v->name) == 0)
			{
				*o = v;
				return true;
			}
			v = v->next;
		}
	}

	CompileUnit *c = elfCompileUnits;

	while (c)
	{
		if (c != u)
		{
			Object *v = c->variables;
			while (v)
			{
				if (strcmp(name, v->name) == 0)
				{
					*o = v;
					return true;
				}
				v = v->next;
			}
		}
		c = c->next;
	}

	return false;
}

char *elfGetSymbol(int i, u32 *value, u32 *size, int *type)
{
	if (i < elfSymbolsCount)
	{
		Symbol *s = &elfSymbols[i];
		*value = s->value;
		*size  = s->size;
		*type  = s->type;
		return s->name;
	}
	return NULL;
}

bool elfGetSymbolAddress(char *sym, u32 *addr, u32 *size, int *type)
{
	if (elfSymbolsCount)
	{
		for (int i = 0; i < elfSymbolsCount; i++)
		{
			Symbol *s = &elfSymbols[i];
			if (strcmp(sym, s->name) == 0)
			{
				*addr = s->value;
				*size = s->size;
				*type = s->type;
				return true;
			}
		}
	}
	return false;
}

ELFfde *elfGetFde(u32 address)
{
	if (elfFdes)
	{
		int i;
		for (i = 0; i < elfFdeCount; i++)
		{
			if (address >= elfFdes[i]->address &&
			    address < elfFdes[i]->end)
			{
				return elfFdes[i];
			}
		}
	}

	return NULL;
}

void elfExecuteCFAInstructions(ELFFrameState *state, u8 *data, u32 len,
                               u32 pc)
{
	u8 *end = data + len;
	int bytes;
	int reg;
	ELFFrameStateRegisters *fs;

	while (data < end && state->pc < pc)
	{
		u8 op = *data++;

		switch (op >> 6)
		{
		case DW_CFA_advance_loc:
			state->pc += (op & 0x3f) * state->codeAlign;
			break;
		case DW_CFA_offset:
			reg = op & 0x3f;
			state->registers.regs[reg].mode   = REG_OFFSET;
			state->registers.regs[reg].offset = state->dataAlign *
			                                    (s32)elfReadLEB128(data, &bytes);
			data += bytes;
			break;
		case DW_CFA_restore:
			// we don't care much about the other possible settings,
			// so just setting to unset is enough for now
			state->registers.regs[op & 0x3f].mode = REG_NOT_SET;
			break;
		case 0:
			switch (op & 0x3f)
			{
			case DW_CFA_nop:
				break;
			case DW_CFA_advance_loc1:
				state->pc += state->codeAlign * (*data++);
				break;
			case DW_CFA_advance_loc2:
				state->pc += state->codeAlign * elfRead2Bytes(data);
				data      += 2;
				break;
			case DW_CFA_advance_loc4:
				state->pc += state->codeAlign * elfRead4Bytes(data);
				data      += 4;
				break;
			case DW_CFA_offset_extended:
				reg   = elfReadLEB128(data, &bytes);
				data += bytes;
				state->registers.regs[reg].mode   = REG_OFFSET;
				state->registers.regs[reg].offset = state->dataAlign *
				                                    (s32)elfReadLEB128(data, &bytes);
				data += bytes;
				break;
			case DW_CFA_restore_extended:
			case DW_CFA_undefined:
			case DW_CFA_same_value:
				reg   = elfReadLEB128(data, &bytes);
				data += bytes;
				state->registers.regs[reg].mode = REG_NOT_SET;
				break;
			case DW_CFA_register:
				reg   = elfReadLEB128(data, &bytes);
				data += bytes;
				state->registers.regs[reg].mode = REG_REGISTER;
				state->registers.regs[reg].reg  = elfReadLEB128(data, &bytes);
				data += bytes;
				break;
			case DW_CFA_remember_state:
				fs = (ELFFrameStateRegisters *)calloc(1,
				                                      sizeof(ELFFrameStateRegisters));
				memcpy(fs, &state->registers, sizeof(ELFFrameStateRegisters));
				state->registers.previous = fs;
				break;
			case DW_CFA_restore_state:
				if (state->registers.previous == NULL)
				{
					printf("Error: previous frame state is NULL.\n");
					return;
				}
				fs = state->registers.previous;
				memcpy(&state->registers, fs, sizeof(ELFFrameStateRegisters));
				free(fs);
				break;
			case DW_CFA_def_cfa:
				state->cfaRegister = elfReadLEB128(data, &bytes);
				data += bytes;
				state->cfaOffset = (s32)elfReadLEB128(data, &bytes);
				data += bytes;
				state->cfaMode = CFA_REG_OFFSET;
				break;
			case DW_CFA_def_cfa_register:
				state->cfaRegister = elfReadLEB128(data, &bytes);
				data += bytes;
				state->cfaMode = CFA_REG_OFFSET;
				break;
			case DW_CFA_def_cfa_offset:
				state->cfaOffset = (s32)elfReadLEB128(data, &bytes);
				data += bytes;
				state->cfaMode = CFA_REG_OFFSET;
				break;
			default:
				printf("Unknown CFA opcode %08x\n", op);
				return;
			}
			break;
		default:
			printf("Unknown CFA opcode %08x\n", op);
			return;
		}
	}
}

ELFFrameState *elfGetFrameState(ELFfde *fde, u32 address)
{
	ELFFrameState *state = (ELFFrameState *)calloc(1, sizeof(ELFFrameState));
	state->pc            = fde->address;
	state->dataAlign     = fde->cie->dataAlign;
	state->codeAlign     = fde->cie->codeAlign;
	state->returnAddress = fde->cie->returnAddress;

	elfExecuteCFAInstructions(state,
	                          fde->cie->data,
	                          fde->cie->dataLen,
	                          0xffffffff);
	elfExecuteCFAInstructions(state,
	                          fde->data,
	                          fde->dataLen,
	                          address);

	return state;
}

void elfPrintCallChain(u32 address)
{
	int count = 1;

	reg_pair regs[15];
	reg_pair newRegs[15];

	memcpy(&regs[0], &reg[0], sizeof(reg_pair) * 15);

	while (count < 20)
	{
		char *addr = elfGetAddressSymbol(address);
		if (*addr == 0)
			addr = "???";

		printf("%08x %s\n", address, addr);

		ELFfde *fde = elfGetFde(address);

		if (fde == NULL)
		{
			break;
		}

		ELFFrameState *state = elfGetFrameState(fde, address);

		if (!state)
		{
			break;
		}

		if (state->cfaMode == CFA_REG_OFFSET)
		{
			memcpy(&newRegs[0], &regs[0], sizeof(reg_pair) * 15);
			u32 addr = 0;
			for (int i = 0; i < 15; i++)
			{
				ELFFrameStateRegister *r = &state->registers.
				                           regs[i];

				switch (r->mode)
				{
				case REG_NOT_SET:
					newRegs[i].I = regs[i].I;
					break;
				case REG_OFFSET:
					newRegs[i].I = elfReadMemory(regs[state->cfaRegister].I +
					                             state->cfaOffset +
					                             r->offset);
					break;
				case REG_REGISTER:
					newRegs[i].I = regs[r->reg].I;
					break;
				default:
					printf("Unknown register mode: %d\n", r->mode);
					break;
				}
			}
			memcpy(regs, newRegs, sizeof(reg_pair)*15);
			addr    = newRegs[14].I;
			addr   &= 0xfffffffe;
			address = addr;
			count++;
		}
		else
		{
			printf("CFA not set\n");
			break;
		}
		if (state->registers.previous)
		{
			ELFFrameStateRegisters *prev = state->registers.previous;

			while (prev)
			{
				ELFFrameStateRegisters *p = prev->previous;
				free(prev);
				prev = p;
			}
		}
		free(state);
	}
}

u32 elfDecodeLocation(Function *f, ELFBlock *o, LocationType *type, u32 base)
{
	u32 framebase = 0;
	if (f && f->frameBase)
	{
		ELFBlock *b = f->frameBase;
		switch (*b->data)
		{
		case DW_OP_reg0:
		case DW_OP_reg1:
		case DW_OP_reg2:
		case DW_OP_reg3:
		case DW_OP_reg4:
		case DW_OP_reg5:
		case DW_OP_reg6:
		case DW_OP_reg7:
		case DW_OP_reg8:
		case DW_OP_reg9:
		case DW_OP_reg10:
		case DW_OP_reg11:
		case DW_OP_reg12:
		case DW_OP_reg13:
		case DW_OP_reg14:
		case DW_OP_reg15:
			framebase = reg[*b->data-0x50].I;
			break;
		default:
			fprintf(stderr, "Unknown frameBase %02x\n", *b->data);
			break;
		}
	}

	ELFBlock *loc      = o;
	u32       location = 0;
	int       bytes    = 0;
	if (loc)
	{
		switch (*loc->data)
		{
		case DW_OP_addr:
			location = elfRead4Bytes(loc->data+1);
			*type    = LOCATION_memory;
			break;
		case DW_OP_plus_uconst:
			location = base + elfReadLEB128(loc->data+1, &bytes);
			*type    = LOCATION_memory;
			break;
		case DW_OP_reg0:
		case DW_OP_reg1:
		case DW_OP_reg2:
		case DW_OP_reg3:
		case DW_OP_reg4:
		case DW_OP_reg5:
		case DW_OP_reg6:
		case DW_OP_reg7:
		case DW_OP_reg8:
		case DW_OP_reg9:
		case DW_OP_reg10:
		case DW_OP_reg11:
		case DW_OP_reg12:
		case DW_OP_reg13:
		case DW_OP_reg14:
		case DW_OP_reg15:
			location = *loc->data - 0x50;
			*type    = LOCATION_register;
			break;
		case DW_OP_fbreg:
		{
			int bytes;
			s32 off = elfReadSignedLEB128(loc->data+1, &bytes);
			location = framebase + off;
			*type    = LOCATION_memory;
			break;
		}
		default:
			fprintf(stderr, "Unknown location %02x\n", *loc->data);
			break;
		}
	}
	return location;
}

u32 elfDecodeLocation(Function *f, ELFBlock *o, LocationType *type)
{
	return elfDecodeLocation(f, o, type, 0);
}

// reading function

u32 elfRead4Bytes(u8 *data)
{
	u32 value = *data++;
	value |= (*data++ << 8);
	value |= (*data++ << 16);
	value |= (*data << 24);
	return value;
}

u16 elfRead2Bytes(u8 *data)
{
	u16 value = *data++;
	value |= (*data << 8);
	return value;
}

char *elfReadString(u8 *data, int *bytesRead)
{
	if (*data == 0)
	{
		*bytesRead = 1;
		return NULL;
	}
	*bytesRead = strlen((char *)data) + 1;
	return (char *)data;
}

s32 elfReadSignedLEB128(u8 *data, int *bytesRead)
{
	s32 result = 0;
	int shift  = 0;
	int count  = 0;

	u8 byte;
	do
	{
		byte = *data++;
		count++;
		result |= (byte & 0x7f) << shift;
		shift  += 7;
	}
	while (byte & 0x80);
	if ((shift < 32) && (byte & 0x40))
		result |= -(1 << shift);
	*bytesRead = count;
	return result;
}

u32 elfReadLEB128(u8 *data, int *bytesRead)
{
	u32 result = 0;
	int shift  = 0;
	int count  = 0;
	u8  byte;
	do
	{
		byte = *data++;
		count++;
		result |= (byte & 0x7f) << shift;
		shift  += 7;
	}
	while (byte & 0x80);
	*bytesRead = count;
	return result;
}

u8 *elfReadSection(u8 *data, ELFSectionHeader *sh)
{
	return data + READ32LE(&sh->offset);
}

ELFSectionHeader *elfGetSectionByName(char *name)
{
	for (int i = 0; i < elfSectionHeadersCount; i++)
	{
		if (strcmp(name,
		           &elfSectionHeadersStringTable[READ32LE(&elfSectionHeaders[i]->
		                                                  name)]) == 0)
		{
			return elfSectionHeaders[i];
		}
	}
	return NULL;
}

ELFSectionHeader *elfGetSectionByNumber(int number)
{
	if (number < elfSectionHeadersCount)
	{
		return elfSectionHeaders[number];
	}
	return NULL;
}

CompileUnit *elfGetCompileUnitForData(u8 *data)
{
	u8 *end = elfCurrentUnit->top + 4 + elfCurrentUnit->length;

	if (data >= elfCurrentUnit->top && data < end)
		return elfCurrentUnit;

	CompileUnit *unit = elfCompileUnits;

	while (unit)
	{
		end = unit->top + 4 + unit->length;

		if (data >= unit->top && data < end)
			return unit;

		unit = unit->next;
	}

	printf("Error: cannot find reference to compile unit at offset %08x\n",
	       (int)(data - elfDebugInfo->infodata));
	exit(-1);
}

u8 *elfReadAttribute(u8 *data, ELFAttr *attr)
{
	int bytes;
	int form = attr->form;
start:
	switch (form)
	{
	case DW_FORM_addr:
		attr->value = elfRead4Bytes(data);
		data       += 4;
		break;
	case DW_FORM_data2:
		attr->value = elfRead2Bytes(data);
		data       += 2;
		break;
	case DW_FORM_data4:
		attr->value = elfRead4Bytes(data);
		data       += 4;
		break;
	case DW_FORM_string:
		attr->string = (char *)data;
		data        += strlen(attr->string)+1;
		break;
	case DW_FORM_strp:
		attr->string = elfDebugStrings + elfRead4Bytes(data);
		data        += 4;
		break;
	case DW_FORM_block:
		attr->block         = (ELFBlock *)malloc(sizeof(ELFBlock));
		attr->block->length = elfReadLEB128(data, &bytes);
		data += bytes;
		attr->block->data = data;
		data += attr->block->length;
		break;
	case DW_FORM_block1:
		attr->block         = (ELFBlock *)malloc(sizeof(ELFBlock));
		attr->block->length = *data++;
		attr->block->data   = data;
		data += attr->block->length;
		break;
	case DW_FORM_data1:
		attr->value = *data++;
		break;
	case DW_FORM_flag:
		attr->flag = (*data++) ? true : false;
		break;
	case DW_FORM_sdata:
		attr->value = elfReadSignedLEB128(data, &bytes);
		data       += bytes;
		break;
	case DW_FORM_udata:
		attr->value = elfReadLEB128(data, &bytes);
		data       += bytes;
		break;
	case DW_FORM_ref_addr:
		attr->value = (elfDebugInfo->infodata + elfRead4Bytes(data)) -
		              elfGetCompileUnitForData(data)->top;
		data += 4;
		break;
	case DW_FORM_ref4:
		attr->value = elfRead4Bytes(data);
		data       += 4;
		break;
	case DW_FORM_ref_udata:
		attr->value = (elfDebugInfo->infodata +
		               (elfGetCompileUnitForData(data)->top -
		                elfDebugInfo->infodata) +
		               elfReadLEB128(data, &bytes)) -
		              elfCurrentUnit->top;
		data += bytes;
		break;
	case DW_FORM_indirect:
		form  = elfReadLEB128(data, &bytes);
		data += bytes;
		goto start;
	default:
		fprintf(stderr, "Unsupported FORM %02x\n", form);
		exit(-1);
	}
	return data;
}

ELFAbbrev *elfGetAbbrev(ELFAbbrev **table, u32 number)
{
	int hash = number % 121;

	ELFAbbrev *abbrev = table[hash];

	while (abbrev)
	{
		if (abbrev->number == number)
			return abbrev;
		abbrev = abbrev->next;
	}
	return NULL;
}

ELFAbbrev * *elfReadAbbrevs(u8 *data, u32 offset)
{
	data += offset;
	ELFAbbrev **abbrevs = (ELFAbbrev * *)calloc(sizeof(ELFAbbrev *)*121, 1);
	int         bytes   = 0;
	u32         number  = elfReadLEB128(data, &bytes);
	data += bytes;
	while (number)
	{
		ELFAbbrev *abbrev = (ELFAbbrev *)calloc(sizeof(ELFAbbrev), 1);

		// read tag information
		abbrev->number = number;
		abbrev->tag    = elfReadLEB128(data, &bytes);
		data += bytes;
		abbrev->hasChildren = *data++ ? true : false;

		// read attributes
		int name = elfReadLEB128(data, &bytes);
		data += bytes;
		int form = elfReadLEB128(data, &bytes);
		data += bytes;

		while (name)
		{
			if ((abbrev->numAttrs % 4) == 0)
			{
				abbrev->attrs = (ELFAttr *)realloc(abbrev->attrs,
				                                   (abbrev->numAttrs + 4) *
				                                   sizeof(ELFAttr));
			}
			abbrev->attrs[abbrev->numAttrs].name   = name;
			abbrev->attrs[abbrev->numAttrs++].form = form;

			name  = elfReadLEB128(data, &bytes);
			data += bytes;
			form  = elfReadLEB128(data, &bytes);
			data += bytes;
		}

		int hash = number % 121;
		abbrev->next  = abbrevs[hash];
		abbrevs[hash] = abbrev;

		number = elfReadLEB128(data, &bytes);
		data  += bytes;

		if (elfGetAbbrev(abbrevs, number) != NULL)
			break;
	}

	return abbrevs;
}

void elfParseCFA(u8 *top)
{
	ELFSectionHeader *h = elfGetSectionByName(".debug_frame");

	if (h == NULL)
	{
		return;
	}

	u8 *data = elfReadSection(top, h);

	u8 *topOffset = data;

	u8 *end = data + READ32LE(&h->size);

	ELFcie *cies = NULL;

	while (data < end)
	{
		u32 offset = data - topOffset;
		u32 len    = elfRead4Bytes(data);
		data += 4;

		u8 *dataEnd = data + len;

		u32 id = elfRead4Bytes(data);
		data += 4;

		if (id == 0xffffffff)
		{
			// skip version
			*data++;

			ELFcie *cie = (ELFcie *)calloc(1, sizeof(ELFcie));

			cie->next = cies;
			cies      = cie;

			cie->offset = offset;

			cie->augmentation = data;
			while (*data)
				data++;
			data++;

			if (*cie->augmentation)
			{
				fprintf(stderr, "Error: augmentation not supported\n");
				exit(-1);
			}

			int bytes;
			cie->codeAlign = elfReadLEB128(data, &bytes);
			data += bytes;

			cie->dataAlign = elfReadSignedLEB128(data, &bytes);
			data += bytes;

			cie->returnAddress = *data++;

			cie->data    = data;
			cie->dataLen = dataEnd - data;
		}
		else
		{
			ELFfde *fde = (ELFfde *)calloc(1, sizeof(ELFfde));

			ELFcie *cie = cies;

			while (cie != NULL)
			{
				if (cie->offset == id)
					break;
				cie = cie->next;
			}

			if (!cie)
			{
				fprintf(stderr, "Cannot find CIE %08x\n", id);
				exit(-1);
			}

			fde->cie = cie;

			fde->address = elfRead4Bytes(data);
			data        += 4;

			fde->end = fde->address + elfRead4Bytes(data);
			data    += 4;

			fde->data    = data;
			fde->dataLen = dataEnd - data;

			if ((elfFdeCount %10) == 0)
			{
				elfFdes = (ELFfde * *)realloc(elfFdes, (elfFdeCount+10) *
				                              sizeof(ELFfde *));
			}
			elfFdes[elfFdeCount++] = fde;
		}
		data = dataEnd;
	}

	elfCies = cies;
}

void elfAddLine(LineInfo *l, u32 a, int file, int line, int *max)
{
	if (l->number == *max)
	{
		*max    += 1000;
		l->lines = (LineInfoItem *)realloc(l->lines, *max*sizeof(LineInfoItem));
	}
	LineInfoItem *li = &l->lines[l->number];
	li->file    = l->files[file-1];
	li->address = a;
	li->line    = line;
	l->number++;
}

void elfParseLineInfo(CompileUnit *unit, u8 *top)
{
	ELFSectionHeader *h = elfGetSectionByName(".debug_line");
	if (h == NULL)
	{
		fprintf(stderr, "No line information found\n");
		return;
	}
	LineInfo *l = unit->lineInfoTable = (LineInfo *)calloc(1, sizeof(LineInfo));
	l->number = 0;
	int max = 1000;
	l->lines = (LineInfoItem *)malloc(1000*sizeof(LineInfoItem));

	u8 *data = elfReadSection(top, h);
	data += unit->lineInfo;
	u32 totalLen = elfRead4Bytes(data);
	data += 4;
	u8 *end = data + totalLen;
	//  u16 version = elfRead2Bytes(data);
	data += 2;
	//  u32 offset = elfRead4Bytes(data);
	data += 4;
	int minInstrSize  = *data++;
	int defaultIsStmt = *data++;
	int lineBase      = (s8)*data++;
	int lineRange     = *data++;
	int opcodeBase    = *data++;
	u8 *stdOpLen      = (u8 *)malloc(opcodeBase * sizeof(u8));
	stdOpLen[0] = 1;
	int i;
	for (i = 1; i < opcodeBase; i++)
		stdOpLen[i] = *data++;

	free(stdOpLen); // todo
	int bytes = 0;

	char *s;
	while ((s = elfReadString(data, &bytes)) != NULL)
	{
		data += bytes;
		//    fprintf(stderr, "Directory is %s\n", s);
	}
	data += bytes;
	int count = 4;
	int index = 0;
	l->files = (char * *)malloc(sizeof(char *)*count);

	while ((s = elfReadString(data, &bytes)) != NULL)
	{
		l->files[index++] = s;

		data += bytes;
		// directory
		elfReadLEB128(data, &bytes);
		data += bytes;
		// time
		elfReadLEB128(data, &bytes);
		data += bytes;
		// size
		elfReadLEB128(data, &bytes);
		data += bytes;
		//    fprintf(stderr, "File is %s\n", s);
		if (index == count)
		{
			count   += 4;
			l->files = (char * *)realloc(l->files, sizeof(char *)*count);
		}
	}
	l->fileCount = index;
	data        += bytes;

	while (data < end)
	{
		u32 address    = 0;
		int file       = 1;
		int line       = 1;
		int col        = 0;
		int isStmt     = defaultIsStmt;
		int basicBlock = 0;
		int endSeq     = 0;

		while (!endSeq)
		{
			int op = *data++;
			switch (op)
			{
			case DW_LNS_extended_op:
			{
				data++;
				op = *data++;
				switch (op)
				{
				case DW_LNE_end_sequence:
					endSeq = 1;
					break;
				case DW_LNE_set_address:
					address = elfRead4Bytes(data);
					data   += 4;
					break;
				default:
					fprintf(stderr, "Unknown extended LINE opcode %02x\n", op);
					exit(-1);
				}
				break;
			}
			case DW_LNS_copy:
				//      fprintf(stderr, "Address %08x line %d (%d)\n", address, line, file);
				elfAddLine(l, address, file, line, &max);
				basicBlock = 0;
				break;
			case DW_LNS_advance_pc:
				address += minInstrSize * elfReadLEB128(data, &bytes);
				data    += bytes;
				break;
			case DW_LNS_advance_line:
				line += elfReadSignedLEB128(data, &bytes);
				data += bytes;
				break;
			case DW_LNS_set_file:
				file  = elfReadLEB128(data, &bytes);
				data += bytes;
				break;
			case DW_LNS_set_column:
				col   = elfReadLEB128(data, &bytes);
				data += bytes;
				break;
			case DW_LNS_negate_stmt:
				isStmt = !isStmt;
				break;
			case DW_LNS_set_basic_block:
				basicBlock = 1;
				break;
			case DW_LNS_const_add_pc:
				address += (minInstrSize *((255 - opcodeBase)/lineRange));
				break;
			case DW_LNS_fixed_advance_pc:
				address += elfRead2Bytes(data);
				data    += 2;
				break;
			default:
				op       = op - opcodeBase;
				address += (op / lineRange) * minInstrSize;
				line    += lineBase + (op % lineRange);
				elfAddLine(l, address, file, line, &max);
				//        fprintf(stderr, "Address %08x line %d (%d)\n", address, line,file);
				basicBlock = 1;
				break;
			}
		}
	}
	l->lines = (LineInfoItem *)realloc(l->lines, l->number*sizeof(LineInfoItem));
}

u8 *elfSkipData(u8 *data, ELFAbbrev *abbrev, ELFAbbrev **abbrevs)
{
	int i;
	int bytes;

	for (i = 0; i < abbrev->numAttrs; i++)
	{
		data = elfReadAttribute(data,  &abbrev->attrs[i]);
		if (abbrev->attrs[i].form == DW_FORM_block1)
			free(abbrev->attrs[i].block);
	}

	if (abbrev->hasChildren)
	{
		int nesting = 1;
		while (nesting)
		{
			u32 abbrevNum = elfReadLEB128(data, &bytes);
			data += bytes;

			if (!abbrevNum)
			{
				nesting--;
				continue;
			}

			abbrev = elfGetAbbrev(abbrevs, abbrevNum);

			for (i = 0; i < abbrev->numAttrs; i++)
			{
				data = elfReadAttribute(data,  &abbrev->attrs[i]);
				if (abbrev->attrs[i].form == DW_FORM_block1)
					free(abbrev->attrs[i].block);
			}

			if (abbrev->hasChildren)
			{
				nesting++;
			}
		}
	}
	return data;
}

Type *elfParseType(CompileUnit *unit, u32);
u8 *elfParseObject(u8 *data, ELFAbbrev *abbrev, CompileUnit *unit,
                   Object **object);
u8 *elfParseFunction(u8 *data, ELFAbbrev *abbrev, CompileUnit *unit,
                     Function **function);
void elfCleanUp(Function *);

void elfAddType(Type *type, CompileUnit *unit, u32 offset)
{
	if (type->next == NULL)
	{
		if (unit->types != type && type->offset == 0)
		{
			type->offset = offset;
			type->next   = unit->types;
			unit->types  = type;
		}
	}
}

void elfParseType(u8 *data, u32 offset, ELFAbbrev *abbrev, CompileUnit *unit,
                  Type **type)
{
	switch (abbrev->tag)
	{
	case DW_TAG_typedef:
	{
		u32   typeref = 0;
		char *name    = NULL;
		for (int i = 0; i < abbrev->numAttrs; i++)
		{
			ELFAttr *attr = &abbrev->attrs[i];
			data = elfReadAttribute(data, attr);
			switch (attr->name)
			{
			case DW_AT_name:
				name = attr->string;
				break;
			case DW_AT_type:
				typeref = attr->value;
				break;
			case DW_AT_decl_file:
			case DW_AT_decl_line:
				break;
			default:
				fprintf(stderr, "Unknown attribute for typedef %02x\n", attr->name);
				break;
			}
		}
		if (abbrev->hasChildren)
			fprintf(stderr, "Unexpected children for typedef\n");
		*type = elfParseType(unit, typeref);
		if (name)
			(*type)->name = name;
		return;
		break;
	}
	case DW_TAG_union_type:
	case DW_TAG_structure_type:
	{
		Type *t = (Type *)calloc(sizeof(Type), 1);
		if (abbrev->tag == DW_TAG_structure_type)
			t->type = TYPE_struct;
		else
			t->type = TYPE_union;

		Struct *s = (Struct *)calloc(sizeof(Struct), 1);
		t->structure = s;
		elfAddType(t, unit, offset);

		for (int i = 0; i < abbrev->numAttrs; i++)
		{
			ELFAttr *attr = &abbrev->attrs[i];
			data = elfReadAttribute(data, attr);
			switch (attr->name)
			{
			case DW_AT_name:
				t->name = attr->string;
				break;
			case DW_AT_byte_size:
				t->size = attr->value;
				break;
			case DW_AT_decl_file:
			case DW_AT_decl_line:
			case DW_AT_sibling:
			case DW_AT_containing_type: // todo?
			case DW_AT_declaration:
			case DW_AT_specification: // TODO:
				break;
			default:
				fprintf(stderr, "Unknown attribute for struct %02x\n", attr->name);
				break;
			}
		}
		if (abbrev->hasChildren)
		{
			int bytes;
			u32 num = elfReadLEB128(data, &bytes);
			data += bytes;
			int index = 0;
			while (num)
			{
				ELFAbbrev *abbr = elfGetAbbrev(unit->abbrevs, num);

				switch (abbr->tag)
				{
				case DW_TAG_member:
				{
					if ((index % 4) == 0)
						s->members = (Member *)realloc(s->members,
						                               sizeof(Member)*(index+4));
					Member *m = &s->members[index];
					m->location  = NULL;
					m->bitOffset = 0;
					m->bitSize   = 0;
					m->byteSize  = 0;
					for (int i = 0; i < abbr->numAttrs; i++)
					{
						ELFAttr *attr = &abbr->attrs[i];
						data = elfReadAttribute(data, attr);
						switch (attr->name)
						{
						case DW_AT_name:
							m->name = attr->string;
							break;
						case DW_AT_type:
							m->type = elfParseType(unit, attr->value);
							break;
						case DW_AT_data_member_location:
							m->location = attr->block;
							break;
						case DW_AT_byte_size:
							m->byteSize = attr->value;
							break;
						case DW_AT_bit_offset:
							m->bitOffset = attr->value;
							break;
						case DW_AT_bit_size:
							m->bitSize = attr->value;
							break;
						case DW_AT_decl_file:
						case DW_AT_decl_line:
						case DW_AT_accessibility:
						case DW_AT_artificial: // todo?
							break;
						default:
							fprintf(stderr, "Unknown member attribute %02x\n",
							        attr->name);
						}
					}
					index++;
					break;
				}
				case DW_TAG_subprogram:
				{
					Function *fnc = NULL;
					data = elfParseFunction(data, abbr, unit, &fnc);
					if (fnc != NULL)
					{
						if (unit->lastFunction)
							unit->lastFunction->next = fnc;
						else
							unit->functions = fnc;
						unit->lastFunction = fnc;
					}
					break;
				}
				case DW_TAG_inheritance:
					// TODO: add support
					data = elfSkipData(data, abbr, unit->abbrevs);
					break;
CASE_TYPE_TAG:
					// skip types... parsed only when used
					data = elfSkipData(data, abbr, unit->abbrevs);
					break;
				case DW_TAG_variable:
					data = elfSkipData(data, abbr, unit->abbrevs);
					break;
				default:
					fprintf(stderr, "Unknown struct tag %02x %s\n", abbr->tag, t->name);
					data = elfSkipData(data, abbr, unit->abbrevs);
					break;
				}
				num   = elfReadLEB128(data, &bytes);
				data += bytes;
			}
			s->memberCount = index;
		}
		*type = t;
		return;
		break;
	}
	case DW_TAG_base_type:
	{
		Type *t = (Type *)calloc(sizeof(Type), 1);

		t->type = TYPE_base;
		elfAddType(t, unit, offset);
		for (int i = 0; i < abbrev->numAttrs; i++)
		{
			ELFAttr *attr = &abbrev->attrs[i];
			data = elfReadAttribute(data, attr);
			switch (attr->name)
			{
			case DW_AT_name:
				t->name = attr->string;
				break;
			case DW_AT_encoding:
				t->encoding = attr->value;
				break;
			case DW_AT_byte_size:
				t->size = attr->value;
				break;
			case DW_AT_bit_size:
				t->bitSize = attr->value;
				break;
			default:
				fprintf(stderr, "Unknown attribute for base type %02x\n",
				        attr->name);
				break;
			}
		}
		if (abbrev->hasChildren)
			fprintf(stderr, "Unexpected children for base type\n");
		*type = t;
		return;
		break;
	}
	case DW_TAG_pointer_type:
	{
		Type *t = (Type *)calloc(sizeof(Type), 1);

		t->type = TYPE_pointer;

		elfAddType(t, unit, offset);

		for (int i = 0; i < abbrev->numAttrs; i++)
		{
			ELFAttr *attr = &abbrev->attrs[i];
			data = elfReadAttribute(data, attr);
			switch (attr->name)
			{
			case DW_AT_type:
				t->pointer = elfParseType(unit, attr->value);
				break;
			case DW_AT_byte_size:
				t->size = attr->value;
				break;
			default:
				fprintf(stderr, "Unknown pointer type attribute %02x\n", attr->name);
				break;
			}
		}
		if (abbrev->hasChildren)
			fprintf(stderr, "Unexpected children for pointer type\n");
		*type = t;
		return;
		break;
	}
	case DW_TAG_reference_type:
	{
		Type *t = (Type *)calloc(sizeof(Type), 1);

		t->type = TYPE_reference;

		elfAddType(t, unit, offset);

		for (int i = 0; i < abbrev->numAttrs; i++)
		{
			ELFAttr *attr = &abbrev->attrs[i];
			data = elfReadAttribute(data, attr);
			switch (attr->name)
			{
			case DW_AT_type:
				t->pointer = elfParseType(unit, attr->value);
				break;
			case DW_AT_byte_size:
				t->size = attr->value;
				break;
			default:
				fprintf(stderr, "Unknown ref type attribute %02x\n", attr->name);
				break;
			}
		}
		if (abbrev->hasChildren)
			fprintf(stderr, "Unexpected children for ref type\n");
		*type = t;
		return;
		break;
	}
	case DW_TAG_volatile_type:
	{
		u32 typeref = 0;

		for (int i = 0; i < abbrev->numAttrs; i++)
		{
			ELFAttr *attr = &abbrev->attrs[i];
			data = elfReadAttribute(data, attr);
			switch (attr->name)
			{
			case DW_AT_type:
				typeref = attr->value;
				break;
			default:
				fprintf(stderr, "Unknown volatile attribute for type %02x\n",
				        attr->name);
				break;
			}
		}
		if (abbrev->hasChildren)
			fprintf(stderr, "Unexpected children for volatile type\n");
		*type = elfParseType(unit, typeref);
		return;
		break;
	}
	case DW_TAG_const_type:
	{
		u32 typeref = 0;

		for (int i = 0; i < abbrev->numAttrs; i++)
		{
			ELFAttr *attr = &abbrev->attrs[i];
			data = elfReadAttribute(data, attr);
			switch (attr->name)
			{
			case DW_AT_type:
				typeref = attr->value;
				break;
			default:
				fprintf(stderr, "Unknown const attribute for type %02x\n",
				        attr->name);
				break;
			}
		}
		if (abbrev->hasChildren)
			fprintf(stderr, "Unexpected children for const type\n");
		*type = elfParseType(unit, typeref);
		return;
		break;
	}
	case DW_TAG_enumeration_type:
	{
		Type *t = (Type *)calloc(sizeof(Type), 1);
		t->type = TYPE_enum;
		Enum *e = (Enum *)calloc(sizeof(Enum), 1);
		t->enumeration = e;
		elfAddType(t, unit, offset);
		int count = 0;
		for (int i = 0; i < abbrev->numAttrs; i++)
		{
			ELFAttr *attr = &abbrev->attrs[i];
			data = elfReadAttribute(data, attr);
			switch (attr->name)
			{
			case DW_AT_name:
				t->name = attr->string;
				break;
			case DW_AT_byte_size:
				t->size = attr->value;
				break;
			case DW_AT_sibling:
			case DW_AT_decl_file:
			case DW_AT_decl_line:
				break;
			default:
				fprintf(stderr, "Unknown enum attribute %02x\n", attr->name);
			}
		}
		if (abbrev->hasChildren)
		{
			int bytes;
			u32 num = elfReadLEB128(data, &bytes);
			data += bytes;
			while (num)
			{
				ELFAbbrev *abbr = elfGetAbbrev(unit->abbrevs, num);

				switch (abbr->tag)
				{
				case DW_TAG_enumerator:
				{
					count++;
					e->members = (EnumMember *)realloc(e->members,
					                                   count*sizeof(EnumMember));
					EnumMember *m = &e->members[count-1];
					for (int i = 0; i < abbr->numAttrs; i++)
					{
						ELFAttr *attr = &abbr->attrs[i];
						data = elfReadAttribute(data, attr);
						switch (attr->name)
						{
						case DW_AT_name:
							m->name = attr->string;
							break;
						case DW_AT_const_value:
							m->value = attr->value;
							break;
						default:
							fprintf(stderr, "Unknown sub param attribute %02x\n",
							        attr->name);
						}
					}
					break;
				}
				default:
					fprintf(stderr, "Unknown enum tag %02x\n", abbr->tag);
					data = elfSkipData(data, abbr, unit->abbrevs);
					break;
				}
				num   = elfReadLEB128(data, &bytes);
				data += bytes;
			}
		}
		e->count = count;
		*type    = t;
		return;
		break;
	}
	case DW_TAG_subroutine_type:
	{
		Type *t = (Type *)calloc(sizeof(Type), 1);
		t->type = TYPE_function;
		FunctionType *f = (FunctionType *)calloc(sizeof(FunctionType), 1);
		t->function = f;
		elfAddType(t, unit, offset);
		for (int i = 0; i < abbrev->numAttrs; i++)
		{
			ELFAttr *attr = &abbrev->attrs[i];
			data = elfReadAttribute(data, attr);
			switch (attr->name)
			{
			case DW_AT_prototyped:
			case DW_AT_sibling:
				break;
			case DW_AT_type:
				f->returnType = elfParseType(unit, attr->value);
				break;
			default:
				fprintf(stderr, "Unknown subroutine attribute %02x\n", attr->name);
			}
		}
		if (abbrev->hasChildren)
		{
			int bytes;
			u32 num = elfReadLEB128(data, &bytes);
			data += bytes;
			Object *lastVar = NULL;
			while (num)
			{
				ELFAbbrev *abbr = elfGetAbbrev(unit->abbrevs, num);

				switch (abbr->tag)
				{
				case DW_TAG_formal_parameter:
				{
					Object *o;
					data = elfParseObject(data, abbr, unit, &o);
					if (f->args)
						lastVar->next = o;
					else
						f->args = o;
					lastVar = o;
					break;
				}
				case DW_TAG_unspecified_parameters:
					// no use in the debugger yet
					data = elfSkipData(data, abbr, unit->abbrevs);
					break;
CASE_TYPE_TAG:
					// skip types... parsed only when used
					data = elfSkipData(data, abbr, unit->abbrevs);
					break;
				default:
					fprintf(stderr, "Unknown subroutine tag %02x\n", abbr->tag);
					data = elfSkipData(data, abbr, unit->abbrevs);
					break;
				}
				num   = elfReadLEB128(data, &bytes);
				data += bytes;
			}
		}
		*type = t;
		return;
		break;
	}
	case DW_TAG_array_type:
	{
		u32    typeref = 0;
		int    i;
		Array *array = (Array *)calloc(sizeof(Array), 1);
		Type * t     = (Type *)calloc(sizeof(Type), 1);
		t->type = TYPE_array;
		elfAddType(t, unit, offset);

		for (i = 0; i < abbrev->numAttrs; i++)
		{
			ELFAttr *attr = &abbrev->attrs[i];
			data = elfReadAttribute(data, attr);
			switch (attr->name)
			{
			case DW_AT_sibling:
				break;
			case DW_AT_type:
				typeref     = attr->value;
				array->type = elfParseType(unit, typeref);
				break;
			default:
				fprintf(stderr, "Unknown array attribute %02x\n", attr->name);
			}
		}
		if (abbrev->hasChildren)
		{
			int bytes;
			u32 num = elfReadLEB128(data, &bytes);
			data += bytes;
			int index     = 0;
			int maxBounds = 0;
			while (num)
			{
				ELFAbbrev *abbr = elfGetAbbrev(unit->abbrevs, num);

				switch (abbr->tag)
				{
				case DW_TAG_subrange_type:
				{
					if (maxBounds == index)
					{
						maxBounds    += 4;
						array->bounds = (int *)realloc(array->bounds,
						                               sizeof(int)*maxBounds);
					}
					for (int i = 0; i < abbr->numAttrs; i++)
					{
						ELFAttr *attr = &abbr->attrs[i];
						data = elfReadAttribute(data, attr);
						switch (attr->name)
						{
						case DW_AT_upper_bound:
							array->bounds[index] = attr->value+1;
							break;
						case DW_AT_type: // ignore
							break;
						default:
							fprintf(stderr, "Unknown subrange attribute %02x\n",
							        attr->name);
						}
					}
					index++;
					break;
				}
				default:
					fprintf(stderr, "Unknown array tag %02x\n", abbr->tag);
					data = elfSkipData(data, abbr, unit->abbrevs);
					break;
				}
				num   = elfReadLEB128(data, &bytes);
				data += bytes;
			}
			array->maxBounds = index;
		}
		t->size = array->type->size;
		for (i = 0; i < array->maxBounds; i++)
			t->size *= array->bounds[i];
		t->array = array;
		*type    = t;
		return;
		break;
	}
	default:
		fprintf(stderr, "Unknown type TAG %02x\n", abbrev->tag);
		exit(-1);
	}
}

Type *elfParseType(CompileUnit *unit, u32 offset)
{
	Type *t = unit->types;

	while (t)
	{
		if (t->offset == offset)
			return t;
		t = t->next;
	}
	if (offset == 0)
	{
		Type *t = (Type *)calloc(sizeof(Type), 1);
		t->type   = TYPE_void;
		t->offset = 0;
		elfAddType(t, unit, 0);
		return t;
	}
	u8 *data = unit->top + offset;
	int bytes;
	int abbrevNum = elfReadLEB128(data, &bytes);
	data += bytes;
	Type *type = NULL;

	ELFAbbrev *abbrev = elfGetAbbrev(unit->abbrevs, abbrevNum);

	elfParseType(data, offset, abbrev, unit, &type);
	return type;
}

void elfGetObjectAttributes(CompileUnit *unit, u32 offset, Object *o)
{
	u8 *data = unit->top + offset;
	int bytes;
	u32 abbrevNum = elfReadLEB128(data, &bytes);
	data += bytes;

	if (!abbrevNum)
	{
		return;
	}

	ELFAbbrev *abbrev = elfGetAbbrev(unit->abbrevs, abbrevNum);

	for (int i = 0; i < abbrev->numAttrs; i++)
	{
		ELFAttr *attr = &abbrev->attrs[i];
		data = elfReadAttribute(data, attr);
		switch (attr->name)
		{
		case DW_AT_location:
			o->location = attr->block;
			break;
		case DW_AT_name:
			if (o->name == NULL)
				o->name = attr->string;
			break;
		case DW_AT_MIPS_linkage_name:
			o->name = attr->string;
			break;
		case DW_AT_decl_file:
			o->file = attr->value;
			break;
		case DW_AT_decl_line:
			o->line = attr->value;
			break;
		case DW_AT_type:
			o->type = elfParseType(unit, attr->value);
			break;
		case DW_AT_external:
			o->external = attr->flag;
			break;
		case DW_AT_const_value:
		case DW_AT_abstract_origin:
		case DW_AT_declaration:
		case DW_AT_artificial:
			// todo
			break;
		case DW_AT_specification:
			// TODO:
			break;
		default:
			fprintf(stderr, "Unknown object attribute %02x\n", attr->name);
			break;
		}
	}
}

u8 *elfParseObject(u8 *data, ELFAbbrev *abbrev, CompileUnit *unit,
                   Object **object)
{
	Object *o = (Object *)calloc(sizeof(Object), 1);

	o->next = NULL;

	for (int i = 0; i < abbrev->numAttrs; i++)
	{
		ELFAttr *attr = &abbrev->attrs[i];
		data = elfReadAttribute(data, attr);
		switch (attr->name)
		{
		case DW_AT_location:
			o->location = attr->block;
			break;
		case DW_AT_name:
			if (o->name == NULL)
				o->name = attr->string;
			break;
		case DW_AT_MIPS_linkage_name:
			o->name = attr->string;
			break;
		case DW_AT_decl_file:
			o->file = attr->value;
			break;
		case DW_AT_decl_line:
			o->line = attr->value;
			break;
		case DW_AT_type:
			o->type = elfParseType(unit, attr->value);
			break;
		case DW_AT_external:
			o->external = attr->flag;
			break;
		case DW_AT_abstract_origin:
			elfGetObjectAttributes(unit, attr->value, o);
			break;
		case DW_AT_const_value:
		case DW_AT_declaration:
		case DW_AT_artificial:
			break;
		case DW_AT_specification:
			// TODO:
			break;
		default:
			fprintf(stderr, "Unknown object attribute %02x\n", attr->name);
			break;
		}
	}
	*object = o;
	return data;
}

u8 *elfParseBlock(u8 *data, ELFAbbrev *abbrev, CompileUnit *unit,
                  Function *func, Object **lastVar)
{
	int bytes;
	u32 start = func->lowPC;
	u32 end   = func->highPC;

	for (int i = 0; i < abbrev->numAttrs; i++)
	{
		ELFAttr *attr = &abbrev->attrs[i];
		data = elfReadAttribute(data, attr);
		switch (attr->name)
		{
		case DW_AT_sibling:
			break;
		case DW_AT_low_pc:
			start = attr->value;
			break;
		case DW_AT_high_pc:
			end = attr->value;
			break;
		case DW_AT_ranges: // ignore for now
			break;
		default:
			fprintf(stderr, "Unknown block attribute %02x\n", attr->name);
			break;
		}
	}

	if (abbrev->hasChildren)
	{
		int nesting = 1;

		while (nesting)
		{
			u32 abbrevNum = elfReadLEB128(data, &bytes);
			data += bytes;

			if (!abbrevNum)
			{
				nesting--;
				continue;
			}

			abbrev = elfGetAbbrev(unit->abbrevs, abbrevNum);

			switch (abbrev->tag)
			{
CASE_TYPE_TAG:       // types only parsed when used
			case DW_TAG_label: // not needed
				data = elfSkipData(data, abbrev, unit->abbrevs);
				break;
			case DW_TAG_lexical_block:
				data = elfParseBlock(data, abbrev, unit, func, lastVar);
				break;
			case DW_TAG_subprogram:
			{
				Function *f = NULL;
				data = elfParseFunction(data, abbrev, unit, &f);
				if (f != NULL)
				{
					if (unit->lastFunction)
						unit->lastFunction->next = f;
					else
						unit->functions = f;
					unit->lastFunction = f;
				}
				break;
			}
			case DW_TAG_variable:
			{
				Object *o;
				data = elfParseObject(data, abbrev, unit, &o);
				if (o->startScope == 0)
					o->startScope = start;
				if (o->endScope == 0)
					o->endScope = 0;
				if (func->variables)
					(*lastVar)->next = o;
				else
					func->variables = o;
				*lastVar = o;
				break;
			}
			case DW_TAG_inlined_subroutine:
				// TODO:
				data = elfSkipData(data, abbrev, unit->abbrevs);
				break;
			default:
			{
				fprintf(stderr, "Unknown block TAG %02x\n", abbrev->tag);
				data = elfSkipData(data, abbrev, unit->abbrevs);
				break;
			}
			}
		}
	}
	return data;
}

void elfGetFunctionAttributes(CompileUnit *unit, u32 offset, Function *func)
{
	u8 *data = unit->top + offset;
	int bytes;
	u32 abbrevNum = elfReadLEB128(data, &bytes);
	data += bytes;

	if (!abbrevNum)
	{
		return;
	}

	ELFAbbrev *abbrev = elfGetAbbrev(unit->abbrevs, abbrevNum);

	for (int i = 0; i < abbrev->numAttrs; i++)
	{
		ELFAttr *attr = &abbrev->attrs[i];
		data = elfReadAttribute(data, attr);

		switch (attr->name)
		{
		case DW_AT_sibling:
			break;
		case DW_AT_name:
			if (func->name == NULL)
				func->name = attr->string;
			break;
		case DW_AT_MIPS_linkage_name:
			func->name = attr->string;
			break;
		case DW_AT_low_pc:
			func->lowPC = attr->value;
			break;
		case DW_AT_high_pc:
			func->highPC = attr->value;
			break;
		case DW_AT_decl_file:
			func->file = attr->value;
			break;
		case DW_AT_decl_line:
			func->line = attr->value;
			break;
		case DW_AT_external:
			func->external = attr->flag;
			break;
		case DW_AT_frame_base:
			func->frameBase = attr->block;
			break;
		case DW_AT_type:
			func->returnType = elfParseType(unit, attr->value);
			break;
		case DW_AT_inline:
		case DW_AT_specification:
		case DW_AT_declaration:
		case DW_AT_artificial:
		case DW_AT_prototyped:
		case DW_AT_proc_body:
		case DW_AT_save_offset:
		case DW_AT_user_2002:
		case DW_AT_virtuality:
		case DW_AT_containing_type:
		case DW_AT_accessibility:
			// todo;
			break;
		case DW_AT_vtable_elem_location:
			free(attr->block);
			break;
		default:
			fprintf(stderr, "Unknown function attribute %02x\n", attr->name);
			break;
		}
	}

	return;
}

u8 *elfParseFunction(u8 *data, ELFAbbrev *abbrev, CompileUnit *unit,
                     Function **f)
{
	Function *func = (Function *)calloc(sizeof(Function), 1);
	*f = func;

	int  bytes;
	bool mangled     = false;
	bool declaration = false;
	for (int i = 0; i < abbrev->numAttrs; i++)
	{
		ELFAttr *attr = &abbrev->attrs[i];
		data = elfReadAttribute(data, attr);
		switch (attr->name)
		{
		case DW_AT_sibling:
			break;
		case DW_AT_name:
			if (func->name == NULL)
				func->name = attr->string;
			break;
		case DW_AT_MIPS_linkage_name:
			func->name = attr->string;
			mangled    = true;
			break;
		case DW_AT_low_pc:
			func->lowPC = attr->value;
			break;
		case DW_AT_high_pc:
			func->highPC = attr->value;
			break;
		case DW_AT_prototyped:
			break;
		case DW_AT_decl_file:
			func->file = attr->value;
			break;
		case DW_AT_decl_line:
			func->line = attr->value;
			break;
		case DW_AT_external:
			func->external = attr->flag;
			break;
		case DW_AT_frame_base:
			func->frameBase = attr->block;
			break;
		case DW_AT_type:
			func->returnType = elfParseType(unit, attr->value);
			break;
		case DW_AT_abstract_origin:
			elfGetFunctionAttributes(unit, attr->value, func);
			break;
		case DW_AT_declaration:
			declaration = attr->flag;
			break;
		case DW_AT_inline:
		case DW_AT_specification:
		case DW_AT_artificial:
		case DW_AT_proc_body:
		case DW_AT_save_offset:
		case DW_AT_user_2002:
		case DW_AT_virtuality:
		case DW_AT_containing_type:
		case DW_AT_accessibility:
			// todo;
			break;
		case DW_AT_vtable_elem_location:
			free(attr->block);
			break;
		default:
			fprintf(stderr, "Unknown function attribute %02x\n", attr->name);
			break;
		}
	}

	if (declaration)
	{
		elfCleanUp(func);
		free(func);
		*f = NULL;

		while (1)
		{
			u32 abbrevNum = elfReadLEB128(data, &bytes);
			data += bytes;

			if (!abbrevNum)
			{
				return data;
			}

			abbrev = elfGetAbbrev(unit->abbrevs, abbrevNum);

			data = elfSkipData(data, abbrev, unit->abbrevs);
		}
	}

	if (abbrev->hasChildren)
	{
		int     nesting   = 1;
		Object *lastParam = NULL;
		Object *lastVar   = NULL;

		while (nesting)
		{
			u32 abbrevNum = elfReadLEB128(data, &bytes);
			data += bytes;

			if (!abbrevNum)
			{
				nesting--;
				continue;
			}

			abbrev = elfGetAbbrev(unit->abbrevs, abbrevNum);

			switch (abbrev->tag)
			{
CASE_TYPE_TAG:       // no need to parse types. only parsed when used
			case DW_TAG_label: // not needed
				data = elfSkipData(data, abbrev, unit->abbrevs);
				break;
			case DW_TAG_subprogram:
			{
				Function *fnc = NULL;
				data = elfParseFunction(data, abbrev, unit, &fnc);
				if (fnc != NULL)
				{
					if (unit->lastFunction == NULL)
						unit->functions = fnc;
					else
						unit->lastFunction->next = fnc;
					unit->lastFunction = fnc;
				}
				break;
			}
			case DW_TAG_lexical_block:
			{
				data = elfParseBlock(data, abbrev, unit, func, &lastVar);
				break;
			}
			case DW_TAG_formal_parameter:
			{
				Object *o;
				data = elfParseObject(data, abbrev, unit, &o);
				if (func->parameters)
					lastParam->next = o;
				else
					func->parameters = o;
				lastParam = o;
				break;
			}
			case DW_TAG_variable:
			{
				Object *o;
				data = elfParseObject(data, abbrev, unit, &o);
				if (func->variables)
					lastVar->next = o;
				else
					func->variables = o;
				lastVar = o;
				break;
			}
			case DW_TAG_unspecified_parameters:
			case DW_TAG_inlined_subroutine:
			{
				// todo
				for (int i = 0; i < abbrev->numAttrs; i++)
				{
					data = elfReadAttribute(data,  &abbrev->attrs[i]);
					if (abbrev->attrs[i].form == DW_FORM_block1)
						free(abbrev->attrs[i].block);
				}

				if (abbrev->hasChildren)
					nesting++;
				break;
			}
			default:
			{
				fprintf(stderr, "Unknown function TAG %02x\n", abbrev->tag);
				data = elfSkipData(data, abbrev, unit->abbrevs);
				break;
			}
			}
		}
	}
	return data;
}

u8 *elfParseUnknownData(u8 *data, ELFAbbrev *abbrev, ELFAbbrev **abbrevs)
{
	int i;
	int bytes;
	//  switch(abbrev->tag) {
	//  default:
	fprintf(stderr, "Unknown TAG %02x\n", abbrev->tag);

	for (i = 0; i < abbrev->numAttrs; i++)
	{
		data = elfReadAttribute(data,  &abbrev->attrs[i]);
		if (abbrev->attrs[i].form == DW_FORM_block1)
			free(abbrev->attrs[i].block);
	}

	if (abbrev->hasChildren)
	{
		int nesting = 1;
		while (nesting)
		{
			u32 abbrevNum = elfReadLEB128(data, &bytes);
			data += bytes;

			if (!abbrevNum)
			{
				nesting--;
				continue;
			}

			abbrev = elfGetAbbrev(abbrevs, abbrevNum);

			fprintf(stderr, "Unknown TAG %02x\n", abbrev->tag);

			for (i = 0; i < abbrev->numAttrs; i++)
			{
				data = elfReadAttribute(data,  &abbrev->attrs[i]);
				if (abbrev->attrs[i].form == DW_FORM_block1)
					free(abbrev->attrs[i].block);
			}

			if (abbrev->hasChildren)
			{
				nesting++;
			}
		}
	}
	//  }
	return data;
}

u8 *elfParseCompileUnitChildren(u8 *data, CompileUnit *unit)
{
	int bytes;
	u32 abbrevNum = elfReadLEB128(data, &bytes);
	data += bytes;
	Object *lastObj = NULL;
	while (abbrevNum)
	{
		ELFAbbrev *abbrev = elfGetAbbrev(unit->abbrevs, abbrevNum);
		switch (abbrev->tag)
		{
		case DW_TAG_subprogram:
		{
			Function *func = NULL;
			data = elfParseFunction(data, abbrev, unit, &func);
			if (func != NULL)
			{
				if (unit->lastFunction)
					unit->lastFunction->next = func;
				else
					unit->functions = func;
				unit->lastFunction = func;
			}
			break;
		}
CASE_TYPE_TAG:
			data = elfSkipData(data, abbrev, unit->abbrevs);
			break;
		case DW_TAG_variable:
		{
			Object *var = NULL;
			data = elfParseObject(data, abbrev, unit, &var);
			if (lastObj)
				lastObj->next = var;
			else
				unit->variables = var;
			lastObj = var;
			break;
		}
		default:
			data = elfParseUnknownData(data, abbrev, unit->abbrevs);
			break;
		}

		abbrevNum = elfReadLEB128(data, &bytes);
		data     += bytes;
	}
	return data;
}

CompileUnit *elfParseCompUnit(u8 *data, u8 *abbrevData)
{
	int bytes;
	u8 *top = data;

	u32 length = elfRead4Bytes(data);
	data += 4;

	u16 version = elfRead2Bytes(data);
	data += 2;

	u32 offset = elfRead4Bytes(data);
	data += 4;

	u8 addrSize = *data++;

	if (version != 2)
	{
		fprintf(stderr, "Unsupported debugging information version %d\n", version);
		return NULL;
	}

	if (addrSize != 4)
	{
		fprintf(stderr, "Unsupported address size %d\n", addrSize);
		return NULL;
	}

	ELFAbbrev **abbrevs = elfReadAbbrevs(abbrevData, offset);

	u32 abbrevNum = elfReadLEB128(data, &bytes);
	data += bytes;

	ELFAbbrev *abbrev = elfGetAbbrev(abbrevs, abbrevNum);

	CompileUnit *unit = (CompileUnit *)calloc(sizeof(CompileUnit), 1);
	unit->top     = top;
	unit->length  = length;
	unit->abbrevs = abbrevs;
	unit->next    = NULL;

	elfCurrentUnit = unit;

	int i;

	for (i = 0; i < abbrev->numAttrs; i++)
	{
		ELFAttr *attr = &abbrev->attrs[i];
		data = elfReadAttribute(data, attr);

		switch (attr->name)
		{
		case DW_AT_name:
			unit->name = attr->string;
			break;
		case DW_AT_stmt_list:
			unit->hasLineInfo = true;
			unit->lineInfo    = attr->value;
			break;
		case DW_AT_low_pc:
			unit->lowPC = attr->value;
			break;
		case DW_AT_high_pc:
			unit->highPC = attr->value;
			break;
		case DW_AT_compdir:
			unit->compdir = attr->string;
			break;
		// ignore
		case DW_AT_language:
		case DW_AT_producer:
		case DW_AT_macro_info:
		case DW_AT_entry_pc:
			break;
		default:
			fprintf(stderr, "Unknown attribute %02x\n", attr->name);
			break;
		}
	}

	if (abbrev->hasChildren)
		elfParseCompileUnitChildren(data, unit);

	return unit;
}

void elfParseAranges(u8 *data)
{
	ELFSectionHeader *sh = elfGetSectionByName(".debug_aranges");
	if (sh == NULL)
	{
		fprintf(stderr, "No aranges found\n");
		return;
	}

	data = elfReadSection(data, sh);
	u8 *end = data + READ32LE(&sh->size);

	int      max    = 4;
	ARanges *ranges = (ARanges *)calloc(sizeof(ARanges), 4);

	int index = 0;

	while (data < end)
	{
		u32 len = elfRead4Bytes(data);
		data += 4;
		//    u16 version = elfRead2Bytes(data);
		data += 2;
		u32 offset = elfRead4Bytes(data);
		data += 4;
		//    u8 addrSize = *data++;
		//    u8 segSize = *data++;
		data += 2; // remove if uncommenting above
		data += 4;
		ranges[index].count  = (len-20)/8;
		ranges[index].offset = offset;
		ranges[index].ranges = (ARange *)calloc(sizeof(ARange), (len-20)/8);
		int i = 0;
		while (true)
		{
			u32 addr = elfRead4Bytes(data);
			data += 4;
			u32 len = elfRead4Bytes(data);
			data += 4;
			if (addr == 0 && len == 0)
				break;
			ranges[index].ranges[i].lowPC  = addr;
			ranges[index].ranges[i].highPC = addr+len;
			i++;
		}
		index++;
		if (index == max)
		{
			max   += 4;
			ranges = (ARanges *)realloc(ranges, max*sizeof(ARanges));
		}
	}
	elfDebugInfo->numRanges = index;
	elfDebugInfo->ranges    = ranges;
}

void elfReadSymtab(u8 *data)
{
	ELFSectionHeader *sh = elfGetSectionByName(".symtab");
	int table = READ32LE(&sh->link);

	char *strtable = (char *)elfReadSection(data, elfGetSectionByNumber(table));

	ELFSymbol *symtab = (ELFSymbol *)elfReadSection(data, sh);

	int count = READ32LE(&sh->size) / sizeof(ELFSymbol);
	elfSymbolsCount = 0;

	elfSymbols = (Symbol *)malloc(sizeof(Symbol)*count);

	int i;

	for (i = 0; i < count; i++)
	{
		ELFSymbol *s       = &symtab[i];
		int        type    = s->info & 15;
		int        binding = s->info >> 4;

		if (binding)
		{
			Symbol *sym = &elfSymbols[elfSymbolsCount];
			sym->name    = &strtable[READ32LE(&s->name)];
			sym->binding = binding;
			sym->type    = type;
			sym->value   = READ32LE(&s->value);
			sym->size    = READ32LE(&s->size);
			elfSymbolsCount++;
		}
	}
	for (i = 0; i < count; i++)
	{
		ELFSymbol *s    = &symtab[i];
		int        bind = s->info>>4;
		int        type = s->info & 15;

		if (!bind)
		{
			Symbol *sym = &elfSymbols[elfSymbolsCount];
			sym->name    = &strtable[READ32LE(&s->name)];
			sym->binding = (s->info >> 4);
			sym->type    = type;
			sym->value   = READ32LE(&s->value);
			sym->size    = READ32LE(&s->size);
			elfSymbolsCount++;
		}
	}
	elfSymbolsStrTab = strtable;
	//  free(symtab);
}

bool elfReadProgram(ELFHeader *eh, u8 *data, int& size, bool parseDebug)
{
	int count = READ16LE(&eh->e_phnum);
	int i;

	if (READ32LE(&eh->e_entry) == 0x2000000)
		cpuIsMultiBoot = true;

	// read program headers... should probably move this code down
	u8 *p = data + READ32LE(&eh->e_phoff);
	size = 0;
	for (i = 0; i < count; i++)
	{
		ELFProgramHeader *ph = (ELFProgramHeader *)p;
		p += sizeof(ELFProgramHeader);
		if (READ16LE(&eh->e_phentsize) != sizeof(ELFProgramHeader))
		{
			p += READ16LE(&eh->e_phentsize) - sizeof(ELFProgramHeader);
		}

		//    printf("PH %d %08x %08x %08x %08x %08x %08x %08x %08x\n",
		//     i, ph->type, ph->offset, ph->vaddr, ph->paddr,
		//     ph->filesz, ph->memsz, ph->flags, ph->align);
		if (cpuIsMultiBoot)
		{
			if (READ32LE(&ph->paddr) >= 0x2000000 &&
			    READ32LE(&ph->paddr) <= 0x203ffff)
			{
				memcpy(&workRAM[READ32LE(&ph->paddr) & 0x3ffff],
				       data + READ32LE(&ph->offset),
				       READ32LE(&ph->filesz));
			}
		}
		else
		{
			if (READ32LE(&ph->paddr) >= 0x8000000 &&
			    READ32LE(&ph->paddr) <= 0x9ffffff)
			{
				memcpy(&rom[READ32LE(&ph->paddr) & 0x1ffffff],
				       data + READ32LE(&ph->offset),
				       READ32LE(&ph->filesz));
				size += READ32LE(&ph->filesz);
			}
		}
	}

	char *stringTable = NULL;

	// read section headers
	p     = data + READ32LE(&eh->e_shoff);
	count = READ16LE(&eh->e_shnum);

	ELFSectionHeader **sh = (ELFSectionHeader * *)
	                        malloc(sizeof(ELFSectionHeader *) * count);

	for (i = 0; i < count; i++)
	{
		sh[i] = (ELFSectionHeader *)p;
		p    += sizeof(ELFSectionHeader);
		if (READ16LE(&eh->e_shentsize) != sizeof(ELFSectionHeader))
			p += READ16LE(&eh->e_shentsize) - sizeof(ELFSectionHeader);
	}

	if (READ16LE(&eh->e_shstrndx) != 0)
	{
		stringTable = (char *)elfReadSection(data,
		                                     sh[READ16LE(&eh->e_shstrndx)]);
	}

	elfSectionHeaders = sh;
	elfSectionHeadersStringTable = stringTable;
	elfSectionHeadersCount       = count;

	for (i = 0; i < count; i++)
	{
		//    printf("SH %d %-20s %08x %08x %08x %08x %08x %08x %08x %08x\n",
		//   i, &stringTable[sh[i]->name], sh[i]->name, sh[i]->type,
		//   sh[i]->flags, sh[i]->addr, sh[i]->offset, sh[i]->size,
		//   sh[i]->link, sh[i]->info);
		if (READ32LE(&sh[i]->flags) & 2) // load section
		{
			if (cpuIsMultiBoot)
			{
				if (READ32LE(&sh[i]->addr) >= 0x2000000 &&
				    READ32LE(&sh[i]->addr) <= 0x203ffff)
				{
					memcpy(&workRAM[READ32LE(&sh[i]->addr) & 0x3ffff], data +
					       READ32LE(&sh[i]->offset),
					       READ32LE(&sh[i]->size));
				}
			}
			else
			{
				if (READ32LE(&sh[i]->addr) >= 0x8000000 &&
				    READ32LE(&sh[i]->addr) <= 0x9ffffff)
				{
					memcpy(&rom[READ32LE(&sh[i]->addr) & 0x1ffffff],
					       data + READ32LE(&sh[i]->offset),
					       READ32LE(&sh[i]->size));
					size += READ32LE(&sh[i]->size);
				}
			}
		}
	}

	if (parseDebug)
	{
		fprintf(stderr, "Parsing debug info\n");

		ELFSectionHeader *dbgHeader = elfGetSectionByName(".debug_info");
		if (dbgHeader == NULL)
		{
			fprintf(stderr, "Cannot find debug information\n");
			goto end;
		}

		ELFSectionHeader *h = elfGetSectionByName(".debug_abbrev");
		if (h == NULL)
		{
			fprintf(stderr, "Cannot find abbreviation table\n");
			goto end;
		}

		elfDebugInfo = (DebugInfo *)calloc(sizeof(DebugInfo), 1);
		u8 *abbrevdata = elfReadSection(data, h);

		h = elfGetSectionByName(".debug_str");

		if (h == NULL)
			elfDebugStrings = NULL;
		else
			elfDebugStrings = (char *)elfReadSection(data, h);

		u8 *debugdata = elfReadSection(data, dbgHeader);

		elfDebugInfo->debugdata = data;
		elfDebugInfo->infodata  = debugdata;

		u32 total = READ32LE(&dbgHeader->size);
		u8 *end   = debugdata + total;
		u8 *ddata = debugdata;

		CompileUnit *last = NULL;
		CompileUnit *unit = NULL;

		while (ddata < end)
		{
			unit         = elfParseCompUnit(ddata, abbrevdata);
			unit->offset = ddata-debugdata;
			elfParseLineInfo(unit, data);
			if (last == NULL)
				elfCompileUnits = unit;
			else
				last->next = unit;
			last   = unit;
			ddata += 4 + unit->length;
		}
		elfParseAranges(data);
		CompileUnit *comp = elfCompileUnits;
		while (comp)
		{
			ARanges *r = elfDebugInfo->ranges;
			for (int i = 0; i < elfDebugInfo->numRanges; i++)
				if (r[i].offset == comp->offset)
				{
					comp->ranges = &r[i];
					break;
				}
			comp = comp->next;
		}
		elfParseCFA(data);
		elfReadSymtab(data);
	}
end:
	if (sh)
	{
		free(sh);
	}

	elfSectionHeaders = NULL;
	elfSectionHeadersStringTable = NULL;
	elfSectionHeadersCount       = 0;

	return true;
}

extern bool8 parseDebug;

bool elfRead(const char *name, int& siz, FILE *f)
{
	fseek(f, 0, SEEK_END);
	long size = ftell(f);
	elfFileData = (u8 *)malloc(size);
	fseek(f, 0, SEEK_SET);
	fread(elfFileData, 1, size, f);
	fclose(f);

	ELFHeader *header = (ELFHeader *)elfFileData;

	if (READ32LE(&header->magic) != 0x464C457F ||
	    READ16LE(&header->e_machine) != 40 ||
	    header->clazz != 1)
	{
		systemMessage(0, N_("Not a valid ELF file %s"), name);
		free(elfFileData);
		elfFileData = NULL;
		return false;
	}

	if (!elfReadProgram(header, elfFileData, siz, parseDebug))
	{
		free(elfFileData);
		elfFileData = NULL;
		return false;
	}

	return true;
}

void elfCleanUp(Object *o)
{
	free(o->location);
}

void elfCleanUp(Function *func)
{
	Object *o = func->parameters;
	while (o)
	{
		elfCleanUp(o);
		Object *next = o->next;
		free(o);
		o = next;
	}

	o = func->variables;
	while (o)
	{
		elfCleanUp(o);
		Object *next = o->next;
		free(o);
		o = next;
	}
	free(func->frameBase);
}

void elfCleanUp(ELFAbbrev **abbrevs)
{
	for (int i = 0; i < 121; i++)
	{
		ELFAbbrev *abbrev = abbrevs[i];

		while (abbrev)
		{
			free(abbrev->attrs);
			ELFAbbrev *next = abbrev->next;
			free(abbrev);

			abbrev = next;
		}
	}
}

void elfCleanUp(Type *t)
{
	switch (t->type)
	{
	case TYPE_function:
		if (t->function)
		{
			Object *o = t->function->args;
			while (o)
			{
				elfCleanUp(o);
				Object *next = o->next;
				free(o);
				o = next;
			}
			free(t->function);
		}
		break;
	case TYPE_array:
		if (t->array)
		{
			free(t->array->bounds);
			free(t->array);
		}
		break;
	case TYPE_struct:
	case TYPE_union:
		if (t->structure)
		{
			for (int i = 0; i < t->structure->memberCount; i++)
			{
				free(t->structure->members[i].location);
			}
			free(t->structure->members);
			free(t->structure);
		}
		break;
	case TYPE_enum:
		if (t->enumeration)
		{
			free(t->enumeration->members);
			free(t->enumeration);
		}
		break;
	case TYPE_base:
	case TYPE_pointer:
	case TYPE_void:
	case TYPE_reference:
		break; // nothing to do
	}
}

void elfCleanUp(CompileUnit *comp)
{
	elfCleanUp(comp->abbrevs);
	free(comp->abbrevs);
	Function *func = comp->functions;
	while (func)
	{
		elfCleanUp(func);
		Function *next = func->next;
		free(func);
		func = next;
	}
	Type *t = comp->types;
	while (t)
	{
		elfCleanUp(t);
		Type *next = t->next;
		free(t);
		t = next;
	}
	Object *o = comp->variables;
	while (o)
	{
		elfCleanUp(o);
		Object *next = o->next;
		free(o);
		o = next;
	}
	if (comp->lineInfoTable)
	{
		free(comp->lineInfoTable->lines);
		free(comp->lineInfoTable->files);
		free(comp->lineInfoTable);
	}
}

void elfCleanUp()
{
	CompileUnit *comp = elfCompileUnits;

	while (comp)
	{
		elfCleanUp(comp);
		CompileUnit *next = comp->next;
		free(comp);
		comp = next;
	}
	elfCompileUnits = NULL;
	free(elfSymbols);
	elfSymbols = NULL;
	//  free(elfSymbolsStrTab);
	elfSymbolsStrTab = NULL;

	elfDebugStrings = NULL;
	if (elfDebugInfo)
	{
		int num = elfDebugInfo->numRanges;
		int i;
		for (i = 0; i < num; i++)
		{
			free(elfDebugInfo->ranges[i].ranges);
		}
		free(elfDebugInfo->ranges);
		free(elfDebugInfo);
		elfDebugInfo = NULL;
	}

	if (elfFdes)
	{
		if (elfFdeCount)
		{
			for (int i = 0; i < elfFdeCount; i++)
				free(elfFdes[i]);
		}
		free(elfFdes);

		elfFdes     = NULL;
		elfFdeCount = 0;
	}

	ELFcie *cie = elfCies;
	while (cie)
	{
		ELFcie *next = cie->next;
		free(cie);
		cie = next;
	}
	elfCies = NULL;

	if (elfFileData)
	{
		free(elfFileData);
		elfFileData = NULL;
	}
}

